/***********************************************************************************************************************
*  OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
*  See also https://openstudio.net/license
***********************************************************************************************************************/

#include "CurveQuadraticLinear.hpp"
#include "CurveQuadraticLinear_Impl.hpp"

#include <utilities/idd/IddFactory.hxx>

#include <utilities/idd/OS_Curve_QuadraticLinear_FieldEnums.hxx>
#include <utilities/idd/IddEnums.hxx>

#include "../utilities/core/Assert.hpp"

#include <cmath>

using namespace std;

namespace openstudio {
namespace model {

  namespace detail {

    CurveQuadraticLinear_Impl::CurveQuadraticLinear_Impl(const IdfObject& idfObject, Model_Impl* model, bool keepHandle)
      : Curve_Impl(idfObject, model, keepHandle) {
      OS_ASSERT(idfObject.iddObject().type() == CurveQuadraticLinear::iddObjectType());
    }

    CurveQuadraticLinear_Impl::CurveQuadraticLinear_Impl(const openstudio::detail::WorkspaceObject_Impl& other, Model_Impl* model, bool keepHandle)
      : Curve_Impl(other, model, keepHandle) {
      OS_ASSERT(other.iddObject().type() == CurveQuadraticLinear::iddObjectType());
    }

    CurveQuadraticLinear_Impl::CurveQuadraticLinear_Impl(const CurveQuadraticLinear_Impl& other, Model_Impl* model, bool keepHandle)
      : Curve_Impl(other, model, keepHandle) {}

    const std::vector<std::string>& CurveQuadraticLinear_Impl::outputVariableNames() const {
      static const std::vector<std::string> result{"Performance Curve Output Value", "Performance Curve Input Variable 1 Value",
                                                   "Performance Curve Input Variable 2 Value"};
      return result;
    }

    IddObjectType CurveQuadraticLinear_Impl::iddObjectType() const {
      return CurveQuadraticLinear::iddObjectType();
    }

    int CurveQuadraticLinear_Impl::numVariables() const {
      return 2;
    }

    double CurveQuadraticLinear_Impl::evaluate(const std::vector<double>& independentVariables) const {
      OS_ASSERT(independentVariables.size() == 2u);

      double x = independentVariables[0];
      if (x < minimumValueofx()) {
        LOG(Warn, "Supplied x is below the minimumValueofx, resetting it.");
        x = minimumValueofx();
      }
      if (x > maximumValueofx()) {
        LOG(Warn, "Supplied x is above the maximumValueofx, resetting it.");
        x = maximumValueofx();
      }

      double y = independentVariables[1];
      if (y < minimumValueofy()) {
        LOG(Warn, "Supplied y is below the minimumValueofy, resetting it.");
        y = minimumValueofy();
      }
      if (y > maximumValueofy()) {
        LOG(Warn, "Supplied y is above the maximumValueofy, resetting it.");
        y = maximumValueofy();
      }

      double x2 = pow(x, 2);
      double result = coefficient1Constant();
      result += coefficient2x() * x;
      result += coefficient3xPOW2() * x2;
      double temp = coefficient4y();
      temp += coefficient5xTIMESY() * x;
      temp += coefficient6xPOW2TIMESY() * x2;
      result += temp * y;

      if (boost::optional<double> _minVal = minimumCurveOutput()) {
        double minVal = _minVal.get();
        if (result < minVal) {
          LOG(Warn, "Calculated curve output is below minimumCurveOutput, resetting it.");
          result = minVal;
        }
      }

      if (boost::optional<double> _maxVal = maximumCurveOutput()) {
        double maxVal = _maxVal.get();
        if (result > maxVal) {
          LOG(Warn, "Calculated curve output is above maximumCurveOutput, resetting it.");
          result = maxVal;
        }
      }

      return result;
    }

    double CurveQuadraticLinear_Impl::coefficient1Constant() const {
      boost::optional<double> value = getDouble(OS_Curve_QuadraticLinearFields::Coefficient1Constant, true);
      OS_ASSERT(value);
      return value.get();
    }

    double CurveQuadraticLinear_Impl::coefficient2x() const {
      boost::optional<double> value = getDouble(OS_Curve_QuadraticLinearFields::Coefficient2x, true);
      OS_ASSERT(value);
      return value.get();
    }

    double CurveQuadraticLinear_Impl::coefficient3xPOW2() const {
      boost::optional<double> value = getDouble(OS_Curve_QuadraticLinearFields::Coefficient3x_POW_2, true);
      OS_ASSERT(value);
      return value.get();
    }

    double CurveQuadraticLinear_Impl::coefficient4y() const {
      boost::optional<double> value = getDouble(OS_Curve_QuadraticLinearFields::Coefficient4y, true);
      OS_ASSERT(value);
      return value.get();
    }

    double CurveQuadraticLinear_Impl::coefficient5xTIMESY() const {
      boost::optional<double> value = getDouble(OS_Curve_QuadraticLinearFields::Coefficient5x_TIMES_y, true);
      OS_ASSERT(value);
      return value.get();
    }

    double CurveQuadraticLinear_Impl::coefficient6xPOW2TIMESY() const {
      boost::optional<double> value = getDouble(OS_Curve_QuadraticLinearFields::Coefficient6x_POW_2_TIMES_y, true);
      OS_ASSERT(value);
      return value.get();
    }

    double CurveQuadraticLinear_Impl::minimumValueofx() const {
      boost::optional<double> value = getDouble(OS_Curve_QuadraticLinearFields::MinimumValueofx, true);
      OS_ASSERT(value);
      return value.get();
    }

    double CurveQuadraticLinear_Impl::maximumValueofx() const {
      boost::optional<double> value = getDouble(OS_Curve_QuadraticLinearFields::MaximumValueofx, true);
      OS_ASSERT(value);
      return value.get();
    }

    double CurveQuadraticLinear_Impl::minimumValueofy() const {
      boost::optional<double> value = getDouble(OS_Curve_QuadraticLinearFields::MinimumValueofy, true);
      OS_ASSERT(value);
      return value.get();
    }

    double CurveQuadraticLinear_Impl::maximumValueofy() const {
      boost::optional<double> value = getDouble(OS_Curve_QuadraticLinearFields::MaximumValueofy, true);
      OS_ASSERT(value);
      return value.get();
    }

    boost::optional<double> CurveQuadraticLinear_Impl::minimumCurveOutput() const {
      return getDouble(OS_Curve_QuadraticLinearFields::MinimumCurveOutput, true);
    }

    boost::optional<double> CurveQuadraticLinear_Impl::maximumCurveOutput() const {
      return getDouble(OS_Curve_QuadraticLinearFields::MaximumCurveOutput, true);
    }

    std::string CurveQuadraticLinear_Impl::inputUnitTypeforX() const {
      boost::optional<std::string> value = getString(OS_Curve_QuadraticLinearFields::InputUnitTypeforX, true);
      OS_ASSERT(value);
      return value.get();
    }

    bool CurveQuadraticLinear_Impl::isInputUnitTypeforXDefaulted() const {
      return isEmpty(OS_Curve_QuadraticLinearFields::InputUnitTypeforX);
    }

    std::string CurveQuadraticLinear_Impl::inputUnitTypeforY() const {
      boost::optional<std::string> value = getString(OS_Curve_QuadraticLinearFields::InputUnitTypeforY, true);
      OS_ASSERT(value);
      return value.get();
    }

    bool CurveQuadraticLinear_Impl::isInputUnitTypeforYDefaulted() const {
      return isEmpty(OS_Curve_QuadraticLinearFields::InputUnitTypeforY);
    }

    std::string CurveQuadraticLinear_Impl::outputUnitType() const {
      boost::optional<std::string> value = getString(OS_Curve_QuadraticLinearFields::OutputUnitType, true);
      OS_ASSERT(value);
      return value.get();
    }

    bool CurveQuadraticLinear_Impl::isOutputUnitTypeDefaulted() const {
      return isEmpty(OS_Curve_QuadraticLinearFields::OutputUnitType);
    }

    bool CurveQuadraticLinear_Impl::setCoefficient1Constant(double coefficient1Constant) {
      bool result = setDouble(OS_Curve_QuadraticLinearFields::Coefficient1Constant, coefficient1Constant);
      OS_ASSERT(result);
      return result;
    }

    bool CurveQuadraticLinear_Impl::setCoefficient2x(double coefficient2x) {
      bool result = setDouble(OS_Curve_QuadraticLinearFields::Coefficient2x, coefficient2x);
      OS_ASSERT(result);
      return result;
    }

    bool CurveQuadraticLinear_Impl::setCoefficient3xPOW2(double coefficient3xPOW2) {
      bool result = setDouble(OS_Curve_QuadraticLinearFields::Coefficient3x_POW_2, coefficient3xPOW2);
      OS_ASSERT(result);
      return result;
    }

    bool CurveQuadraticLinear_Impl::setCoefficient4y(double coefficient4y) {
      bool result = setDouble(OS_Curve_QuadraticLinearFields::Coefficient4y, coefficient4y);
      OS_ASSERT(result);
      return result;
    }

    bool CurveQuadraticLinear_Impl::setCoefficient5xTIMESY(double coefficient5xTIMESY) {
      bool result = setDouble(OS_Curve_QuadraticLinearFields::Coefficient5x_TIMES_y, coefficient5xTIMESY);
      OS_ASSERT(result);
      return result;
    }

    bool CurveQuadraticLinear_Impl::setCoefficient6xPOW2TIMESY(double coefficient6xPOW2TIMESY) {
      bool result = setDouble(OS_Curve_QuadraticLinearFields::Coefficient6x_POW_2_TIMES_y, coefficient6xPOW2TIMESY);
      OS_ASSERT(result);
      return result;
    }

    bool CurveQuadraticLinear_Impl::setMinimumValueofx(double minimumValueofx) {
      bool result = setDouble(OS_Curve_QuadraticLinearFields::MinimumValueofx, minimumValueofx);
      OS_ASSERT(result);
      return result;
    }

    bool CurveQuadraticLinear_Impl::setMaximumValueofx(double maximumValueofx) {
      bool result = setDouble(OS_Curve_QuadraticLinearFields::MaximumValueofx, maximumValueofx);
      OS_ASSERT(result);
      return result;
    }

    bool CurveQuadraticLinear_Impl::setMinimumValueofy(double minimumValueofy) {
      bool result = setDouble(OS_Curve_QuadraticLinearFields::MinimumValueofy, minimumValueofy);
      OS_ASSERT(result);
      return result;
    }

    bool CurveQuadraticLinear_Impl::setMaximumValueofy(double maximumValueofy) {
      bool result = setDouble(OS_Curve_QuadraticLinearFields::MaximumValueofy, maximumValueofy);
      OS_ASSERT(result);
      return result;
    }

    bool CurveQuadraticLinear_Impl::setMinimumCurveOutput(boost::optional<double> minimumCurveOutput) {
      bool result = false;
      if (minimumCurveOutput) {
        result = setDouble(OS_Curve_QuadraticLinearFields::MinimumCurveOutput, minimumCurveOutput.get());
      } else {
        result = setString(OS_Curve_QuadraticLinearFields::MinimumCurveOutput, "");
      }
      OS_ASSERT(result);
      return result;
    }

    void CurveQuadraticLinear_Impl::resetMinimumCurveOutput() {
      bool result = setString(OS_Curve_QuadraticLinearFields::MinimumCurveOutput, "");
      OS_ASSERT(result);
    }

    bool CurveQuadraticLinear_Impl::setMaximumCurveOutput(boost::optional<double> maximumCurveOutput) {
      bool result = false;
      if (maximumCurveOutput) {
        result = setDouble(OS_Curve_QuadraticLinearFields::MaximumCurveOutput, maximumCurveOutput.get());
      } else {
        result = setString(OS_Curve_QuadraticLinearFields::MaximumCurveOutput, "");
      }
      OS_ASSERT(result);
      return result;
    }

    void CurveQuadraticLinear_Impl::resetMaximumCurveOutput() {
      bool result = setString(OS_Curve_QuadraticLinearFields::MaximumCurveOutput, "");
      OS_ASSERT(result);
    }

    bool CurveQuadraticLinear_Impl::setInputUnitTypeforX(const std::string& inputUnitTypeforX) {
      bool result = setString(OS_Curve_QuadraticLinearFields::InputUnitTypeforX, inputUnitTypeforX);
      return result;
    }

    void CurveQuadraticLinear_Impl::resetInputUnitTypeforX() {
      bool result = setString(OS_Curve_QuadraticLinearFields::InputUnitTypeforX, "");
      OS_ASSERT(result);
    }

    bool CurveQuadraticLinear_Impl::setInputUnitTypeforY(const std::string& inputUnitTypeforY) {
      bool result = setString(OS_Curve_QuadraticLinearFields::InputUnitTypeforY, inputUnitTypeforY);
      return result;
    }

    void CurveQuadraticLinear_Impl::resetInputUnitTypeforY() {
      bool result = setString(OS_Curve_QuadraticLinearFields::InputUnitTypeforY, "");
      OS_ASSERT(result);
    }

    bool CurveQuadraticLinear_Impl::setOutputUnitType(const std::string& outputUnitType) {
      bool result = setString(OS_Curve_QuadraticLinearFields::OutputUnitType, outputUnitType);
      return result;
    }

    void CurveQuadraticLinear_Impl::resetOutputUnitType() {
      bool result = setString(OS_Curve_QuadraticLinearFields::OutputUnitType, "");
      OS_ASSERT(result);
    }

  }  // namespace detail

  CurveQuadraticLinear::CurveQuadraticLinear(const Model& model) : Curve(CurveQuadraticLinear::iddObjectType(), model) {
    OS_ASSERT(getImpl<detail::CurveQuadraticLinear_Impl>());
    setDouble(OS_Curve_QuadraticLinearFields::Coefficient1Constant, 0.0);
    setDouble(OS_Curve_QuadraticLinearFields::Coefficient2x, 0.0);
    setDouble(OS_Curve_QuadraticLinearFields::Coefficient3x_POW_2, 0.0);
    setDouble(OS_Curve_QuadraticLinearFields::Coefficient4y, 0.0);
    setDouble(OS_Curve_QuadraticLinearFields::Coefficient5x_TIMES_y, 0.0);
    setDouble(OS_Curve_QuadraticLinearFields::Coefficient6x_POW_2_TIMES_y, 1.0);
    setDouble(OS_Curve_QuadraticLinearFields::MinimumValueofx, 0.0);
    setDouble(OS_Curve_QuadraticLinearFields::MaximumValueofx, 1.0);
    setDouble(OS_Curve_QuadraticLinearFields::MinimumValueofy, 0.0);
    setDouble(OS_Curve_QuadraticLinearFields::MaximumValueofy, 1.0);
  }

  IddObjectType CurveQuadraticLinear::iddObjectType() {
    IddObjectType result(IddObjectType::OS_Curve_QuadraticLinear);
    return result;
  }

  std::vector<std::string> CurveQuadraticLinear::validInputUnitTypeforXValues() {
    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_Curve_QuadraticLinearFields::InputUnitTypeforX);
  }

  std::vector<std::string> CurveQuadraticLinear::validInputUnitTypeforYValues() {
    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_Curve_QuadraticLinearFields::InputUnitTypeforY);
  }

  std::vector<std::string> CurveQuadraticLinear::validOutputUnitTypeValues() {
    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_Curve_QuadraticLinearFields::OutputUnitType);
  }

  double CurveQuadraticLinear::coefficient1Constant() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->coefficient1Constant();
  }

  double CurveQuadraticLinear::coefficient2x() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->coefficient2x();
  }

  double CurveQuadraticLinear::coefficient3xPOW2() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->coefficient3xPOW2();
  }

  double CurveQuadraticLinear::coefficient4y() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->coefficient4y();
  }

  double CurveQuadraticLinear::coefficient5xTIMESY() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->coefficient5xTIMESY();
  }

  double CurveQuadraticLinear::coefficient6xPOW2TIMESY() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->coefficient6xPOW2TIMESY();
  }

  double CurveQuadraticLinear::minimumValueofx() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->minimumValueofx();
  }

  double CurveQuadraticLinear::maximumValueofx() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->maximumValueofx();
  }

  double CurveQuadraticLinear::minimumValueofy() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->minimumValueofy();
  }

  double CurveQuadraticLinear::maximumValueofy() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->maximumValueofy();
  }

  boost::optional<double> CurveQuadraticLinear::minimumCurveOutput() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->minimumCurveOutput();
  }

  boost::optional<double> CurveQuadraticLinear::maximumCurveOutput() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->maximumCurveOutput();
  }

  std::string CurveQuadraticLinear::inputUnitTypeforX() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->inputUnitTypeforX();
  }

  bool CurveQuadraticLinear::isInputUnitTypeforXDefaulted() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->isInputUnitTypeforXDefaulted();
  }

  std::string CurveQuadraticLinear::inputUnitTypeforY() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->inputUnitTypeforY();
  }

  bool CurveQuadraticLinear::isInputUnitTypeforYDefaulted() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->isInputUnitTypeforYDefaulted();
  }

  std::string CurveQuadraticLinear::outputUnitType() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->outputUnitType();
  }

  bool CurveQuadraticLinear::isOutputUnitTypeDefaulted() const {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->isOutputUnitTypeDefaulted();
  }

  bool CurveQuadraticLinear::setCoefficient1Constant(double coefficient1Constant) {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->setCoefficient1Constant(coefficient1Constant);
  }

  bool CurveQuadraticLinear::setCoefficient2x(double coefficient2x) {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->setCoefficient2x(coefficient2x);
  }

  bool CurveQuadraticLinear::setCoefficient3xPOW2(double coefficient3xPOW2) {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->setCoefficient3xPOW2(coefficient3xPOW2);
  }

  bool CurveQuadraticLinear::setCoefficient4y(double coefficient4y) {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->setCoefficient4y(coefficient4y);
  }

  bool CurveQuadraticLinear::setCoefficient5xTIMESY(double coefficient5xTIMESY) {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->setCoefficient5xTIMESY(coefficient5xTIMESY);
  }

  bool CurveQuadraticLinear::setCoefficient6xPOW2TIMESY(double coefficient6xPOW2TIMESY) {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->setCoefficient6xPOW2TIMESY(coefficient6xPOW2TIMESY);
  }

  bool CurveQuadraticLinear::setMinimumValueofx(double minimumValueofx) {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->setMinimumValueofx(minimumValueofx);
  }

  bool CurveQuadraticLinear::setMaximumValueofx(double maximumValueofx) {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->setMaximumValueofx(maximumValueofx);
  }

  bool CurveQuadraticLinear::setMinimumValueofy(double minimumValueofy) {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->setMinimumValueofy(minimumValueofy);
  }

  bool CurveQuadraticLinear::setMaximumValueofy(double maximumValueofy) {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->setMaximumValueofy(maximumValueofy);
  }

  bool CurveQuadraticLinear::setMinimumCurveOutput(double minimumCurveOutput) {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->setMinimumCurveOutput(minimumCurveOutput);
  }

  void CurveQuadraticLinear::resetMinimumCurveOutput() {
    getImpl<detail::CurveQuadraticLinear_Impl>()->resetMinimumCurveOutput();
  }

  bool CurveQuadraticLinear::setMaximumCurveOutput(double maximumCurveOutput) {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->setMaximumCurveOutput(maximumCurveOutput);
  }

  void CurveQuadraticLinear::resetMaximumCurveOutput() {
    getImpl<detail::CurveQuadraticLinear_Impl>()->resetMaximumCurveOutput();
  }

  bool CurveQuadraticLinear::setInputUnitTypeforX(const std::string& inputUnitTypeforX) {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->setInputUnitTypeforX(inputUnitTypeforX);
  }

  void CurveQuadraticLinear::resetInputUnitTypeforX() {
    getImpl<detail::CurveQuadraticLinear_Impl>()->resetInputUnitTypeforX();
  }

  bool CurveQuadraticLinear::setInputUnitTypeforY(const std::string& inputUnitTypeforY) {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->setInputUnitTypeforY(inputUnitTypeforY);
  }

  void CurveQuadraticLinear::resetInputUnitTypeforY() {
    getImpl<detail::CurveQuadraticLinear_Impl>()->resetInputUnitTypeforY();
  }

  bool CurveQuadraticLinear::setOutputUnitType(const std::string& outputUnitType) {
    return getImpl<detail::CurveQuadraticLinear_Impl>()->setOutputUnitType(outputUnitType);
  }

  void CurveQuadraticLinear::resetOutputUnitType() {
    getImpl<detail::CurveQuadraticLinear_Impl>()->resetOutputUnitType();
  }

  /// @cond
  CurveQuadraticLinear::CurveQuadraticLinear(std::shared_ptr<detail::CurveQuadraticLinear_Impl> impl) : Curve(std::move(impl)) {}
  /// @endcond

}  // namespace model
}  // namespace openstudio
